﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Text;

namespace MishraReader
{
    /// <summary>
    ///   Send and receive a specific message from another component with a weak event pattern
    /// </summary>
    internal class LightWeakEventManager : ILightWeakEventManager
    {
        // Fields
        private readonly MessageToActionsMap _messageToActionsMap = new MessageToActionsMap();

        // Methods

        public void Register<T>(Object message, Action<T> callback)
        {
            Register(message, callback, typeof(T));
        }

        public void Register(Object message, Action callback)
        {
            Register(message, callback, null);
        }

        public void Send(Object message)
        {
            Type type;
            if (_messageToActionsMap.TryGetParameterType(message, out type) && (type != null))
            {
                throw new TargetParameterCountException(string.Format("Must pass a parameter of type {0} with this message. Registered action(s) expect it.",
                                                                      type.FullName));
            }
            var actions = _messageToActionsMap.GetActions(message);
            if (actions != null)
            {
                actions.ForEach(action => action.DynamicInvoke(new object[0]));
            }
        }

        public void Send(object message, object parameter)
        {
            Type type;
            if (_messageToActionsMap.TryGetParameterType(message, out type) && (type == null))
            {
                throw new TargetParameterCountException(string.Format("Cannot pass a parameter with message '{0}'. Registered action(s) expect no parameter.",
                                                                      message));
            }
            var actions = _messageToActionsMap.GetActions(message);
            if (actions != null)
            {
                actions.ForEach(action => action.DynamicInvoke(new[] {parameter}));
            }
        }

        public void Unregister(Object message, Action callback)
        {
            Unregister(message, callback, null);
        }

        public void Unregister<T>(Object message, Action<T> callback)
        {
            Unregister(message, callback, typeof(T));
        }

        private void Register(object message, Delegate callback, Type parameterType)
        {
            _messageToActionsMap.AddAction(message, callback.Target, callback.GetMethodInfo(), parameterType);
        }

        private void Unregister(object message, Delegate callback, Type parameterType)
        {
            
            _messageToActionsMap.RemoveAction(message, callback.Target, callback.GetMethodInfo(), parameterType);
        }


        [Conditional("DEBUG")]
        private void VerifyParameterType(Object message, Type parameterType)
        {
            Type type = null;
            if (_messageToActionsMap.TryGetParameterType(message, out type))
            {
                if ((type != null) && (parameterType != null))
                {
                    if (!type.Equals(parameterType))
                    {
                        throw new InvalidOperationException(
                            string.Format(
                                "The registered action's parameter type is inconsistent with the previously registered actions for message '{0}'.\nExpected: {1}\nAdding: {2}",
                                message, type.FullName, parameterType.FullName));
                    }
                }
                else if (type != parameterType)
                {
                    throw new TargetParameterCountException(
                        string.Format(
                            "The registered action has a number of parameters inconsistent with the previously registered actions for message \"{0}\".\nExpected: {1}\nAdding: {2}",
                            message, (type == null) ? 0 : 1, (parameterType == null) ? 0 : 1));
                }
            }
        }

        // Nested Types
        private class MessageToActionsMap
        {
            // Fields
            private readonly Dictionary<Object, List<WeakAction>> _map = new Dictionary<Object, List<WeakAction>>();

            // Methods


            internal void AddAction(Object message, object target, MethodInfo method, Type actionType)
            {
                lock (_map)
                {
                    if (!_map.ContainsKey(message))
                    {
                        _map[message] = new List<WeakAction>();
                    }
                    _map[message].Add(new WeakAction(target, method, actionType));
                }
            }

            internal List<Delegate> GetActions(Object message)
            {
                List<Delegate> list;
                lock (_map)
                {
                    if (!_map.ContainsKey(message))
                    {
                        return null;
                    }
                    var list2 = _map[message];
                    list = new List<Delegate>(list2.Count);
                    for (var i = list2.Count - 1; i > -1; i--)
                    {
                        var item = list2[i];
                        if (item != null)
                        {
                            var delegate2 = item.CreateAction();
                            if (delegate2 != null)
                            {
                                list.Add(delegate2);
                            }
                            else
                            {
                                list2.Remove(item);
                            }
                        }
                    }
                    if (list2.Count == 0)
                    {
                        _map.Remove(message);
                    }
                }
                list.Reverse();
                return list;
            }

            internal void RemoveAction(Object message, object target, MethodInfo method, Type actionType)
            {
                lock (_map)
                {
                    List<WeakAction> list;
                    if (_map.TryGetValue(message, out list))
                    {
                        WeakAction weakActionToRemove = null;
                        foreach (var weakAction in list)
                        {
                            if (weakAction._method == method && weakAction._targetRef.IsAlive && weakAction._targetRef.Target == target)
                            {
                                weakActionToRemove = weakAction;
                                break;
                            }
                        }

                        if (weakActionToRemove != null)
                        {
                            list.Remove(weakActionToRemove);

                            if (list.Count == 0)
                                _map.Remove(message);
                        }
                    }
                }
            }

            internal bool TryGetParameterType(Object message, out Type parameterType)
            {
                List<WeakAction> list;
                parameterType = null;
                lock (_map)
                {
                    if (!_map.TryGetValue(message, out list) || (list.Count == 0))
                    {
                        return false;
                    }
                }
                parameterType = list[0].ParameterType;
                return true;
            }
        }

        private class WeakAction
        {
            // Fields
            internal readonly Type ParameterType;
            internal readonly MethodInfo _method;
            internal readonly WeakReference _targetRef;
            private readonly Type _delegateType;

            // Methods
            internal WeakAction(object target, MethodInfo method, Type parameterType)
            {
                if (target == null)
                {
                    _targetRef = null;
                }
                else
                {
                    _targetRef = new WeakReference(target);
                }
                _method = method;
                ParameterType = parameterType;
                if (parameterType == null)
                {
                    _delegateType = typeof(Action);
                }
                else
                {
                    _delegateType = typeof(Action<>).MakeGenericType(new[] {parameterType});
                }
            }

            internal Delegate CreateAction()
            {
                if (_targetRef == null)
                {
                    return _method.CreateDelegate(_delegateType);
                }
                try
                {
                    var target = _targetRef.Target;
                    if (target != null)
                    {
                        return _method.CreateDelegate(_delegateType, target);
                    }
                }
                catch
                {
                }
                return null;
            }
        }
    }
}